#include "types.h"
#include "globals.h"
#include "errno.h"

#include "util/debug.h"
#include "util/string.h"

#include "proc/proc.h"
#include "proc/kthread.h"

#include "mm/mm.h"
#include "mm/mman.h"
#include "mm/page.h"
#include "mm/pframe.h"
#include "mm/mmobj.h"
#include "mm/pagetable.h"
#include "mm/tlb.h"

#include "fs/file.h"
#include "fs/vnode.h"

#include "vm/shadow.h"
#include "vm/vmmap.h"

#include "api/exec.h"

#include "main/interrupt.h"

/* Pushes the appropriate things onto the kernel stack of a newly forked thread
 * so that it can begin execution in userland_entry.
 * regs: registers the new thread should have on execution
 * kstack: location of the new thread's kernel stack
 * Returns the new stack pointer on success. */
static uint32_t
fork_setup_stack(const regs_t *regs, void *kstack)
{
        /* Pointer argument and dummy return address, and userland dummy return
         * address */
        uint32_t esp = ((uint32_t) kstack) + DEFAULT_STACK_SIZE - (sizeof(regs_t) + 12);
        *(void **)(esp + 4) = (void *)(esp + 8); /* Set the argument to point to location of struct on stack */
        memcpy((void *)(esp + 8), regs, sizeof(regs_t)); /* Copy over struct */
        return esp;
}


/*
 * The implementation of fork(2). Once this works,
 * you're practically home free. This is what the
 * entirety of Weenix has been leading up to.
 * Go forth and conquer.
 */
int
do_fork(struct regs *regs)
{
KASSERT(regs != NULL);
dbg(DBG_ELF,"do_fork: regs is not null\n");
KASSERT(curproc != NULL);
dbg(DBG_ELF,"do_fork: curproc is not null\n");
KASSERT(curproc->p_state == PROC_RUNNING);
dbg(DBG_ELF,"do_fork: current process's p_state is PROC_RUNNING\n");

	proc_t *dupproc = proc_create(curproc->p_comm); /* giving the same name */

	if(dupproc==NULL)
		panic("\nIn do_fork() : new proc not created\n");

	dupproc->p_vmmap = vmmap_clone(curproc->p_vmmap);
	
	if(dupproc->p_vmmap==NULL)
		panic("\nIn do_fork() : child vmmap not cloned\n");

	list_link_t *dlink,*clink;
	vmarea_t *dvma,*cvma;
	for (dlink = dupproc->p_vmmap->vmm_list.l_next,clink = curproc->p_vmmap->vmm_list.l_next ; dlink != &(dupproc->p_vmmap->vmm_list) && clink != &(curproc->p_vmmap->vmm_list); dlink = dlink->l_next,clink = clink->l_next)
	{
		dvma = list_item(dlink, vmarea_t, vma_plink);
		cvma = list_item(clink, vmarea_t, vma_plink);
		if(dvma->vma_start != cvma->vma_start)
			panic("\nIn do_fork(): 2 vmareas not matching");
		if(dvma->vma_flags & MAP_SHARED)
		{
			dvma->vma_obj = cvma->vma_obj;
			list_insert_tail(&(dvma->vma_obj->mmo_un.mmo_vmas),&(dvma->vma_olink));
			dvma->vma_obj->mmo_ops->ref(dvma->vma_obj);	
		}
		else if(dvma->vma_flags & MAP_PRIVATE)
		{
			mmobj_t *dshadow,*cshadow;
			cvma->vma_obj->mmo_ops->ref(cvma->vma_obj);
			dshadow =  shadow_create();
			dshadow->mmo_un.mmo_bottom_obj = dvma->vma_obj->mmo_un.mmo_bottom_obj;
/*			dshadow->mmo_ops->ref(dshadow);*/
			cshadow =  shadow_create();
/*			cshadow->mmo_ops->ref(cshadow);*/
			cshadow->mmo_un.mmo_bottom_obj = cvma->vma_obj->mmo_un.mmo_bottom_obj;
			dshadow->mmo_shadowed = cvma->vma_obj;
			cshadow->mmo_shadowed = cvma->vma_obj;
			cvma->vma_obj = cshadow;
			dvma->vma_obj = dshadow;
		}
		else
			panic("\nIn do_fork(): found a vmarea having neither of MAP_SHARED or MAP_PRIVATE flags");
	}
	pt_unmap_range(curproc->p_pagedir,USER_MEM_LOW, USER_MEM_HIGH);
	tlb_flush_all();
	kthread_t *dthr = kthread_clone(curthr);
	KASSERT(dthr->kt_kstack != NULL);
	dbg(DBG_ELF,"do_fork: new thread's stack is not null\n");
	dthr->kt_ctx.c_eip = curthr->kt_ctx.c_eip;
	dthr->kt_ctx.c_esp = fork_setup_stack(regs, &(dthr->kt_ctx.c_kstack));
	dupproc->p_cwd = curproc->p_cwd;
	vref(dupproc->p_cwd);
	int i;
	for(i=0;i<NFILES;i++)
	{
		dupproc->p_files[i] = curproc->p_files[i];
		if(dupproc->p_files[i]!=NULL)
			fref(dupproc->p_files[i]);
	}
	dupproc->p_status = curproc->p_status; 
        dupproc->p_state =  curproc->p_state;        
        dupproc->p_wait =  curproc->p_wait;     
	dupproc->p_pagedir = curproc->p_pagedir;

	KASSERT(dupproc->p_state == PROC_RUNNING);
	dbg(DBG_ELF,"do_fork: new process's p_state is PROC_RUNNING\n");
	KASSERT(dupproc->p_pagedir != NULL);
	dbg(DBG_ELF,"do_fork: new process's pgdir is not null\n");

	sched_make_runnable(dthr);
	return regs->r_eax;	
        /*NOT_YET_IMPLEMENTED("VM: do_fork");
        return 0;*/
}
